#  This file is part of OpenLightingDesigner.
# 
#  OpenLightingDesigner is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  OpenLightingDesigner is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with OpenLightingDesigner.  If not, see <http://www.gnu.org/licenses/>.
#
# AttributeGroup.py
# Copyright (c) 2010 - 2011 Matthew Reid

import colorsys
from Bases import OSCBaseObject
from Bases import setID
from Bases.Properties import EMULATED_TYPES
from Group import GroupBase
from ..Attributes import Parameter

class AttributeGroup(GroupBase):
    _saved_attributes = ['member_values', '_value']
    signals_to_register = ['value_changed']
    #_Properties = {'Value_Property':dict(fget='_Value_Property_getter', fset='_Value_Property_setter', quiet=True)}
    _Properties = {'captured_by':dict(default=None, ignore_type=True, quiet=True), 
                   'ActivePalettes':dict(default={}, quiet=True)}
    value_min = 0
    value_max = 255
    value_symbol = ''
    def __init__(self, **kwargs):
        self._device = kwargs.get('device')
        self._parent_group = kwargs.get('parent_group')
        self.palette_parents = {}
        self.value_set_by_osc = False
        self.value_set_by_parent = False
        self.edited = False
        #self.captured_by = None
        
        if kwargs.get('osc_address') is None:
            kwargs.setdefault('osc_address', self._saved_class_name)
        if 'deserialize' not in kwargs:
            self.member_values = {}
        super(AttributeGroup, self).__init__(**kwargs)
        self.bind(captured_by=self._on_captured_by_set, 
                  ActivePalettes=self._on_ActivePalettes_changed)
        if self.parent is not None:
            self.parent.bind(ActivePalettes=self.on_parent_ActivePalettes_changed)
        
        #if self.valid:
        #    self.value = self.get_value_from_attributes()
            
        if not self.valid and len(self.members) == 1:
            member = self.members[self.members.keys()[0]]
            if hasattr(member, 'edited'):
                self.value = member.value
                self.edited = member.edited
                
        ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
#        if self.osc_enabled:
#            self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value, 
#                                            'current-value':self.on_osc_request_value})
            
            
    def _on_ActivePalettes_changed(self, **kwargs):
        pass
        #print self, kwargs
    
    @property
    def device(self):
        return self._device
    @device.setter
    def device(self, value):
        self._device = value
        self.valid = self.search_for_members()
        if self.valid:
            self.value = self.get_value_from_attributes()
            for member in self.members.values():
                member.connect('value_changed', self.on_attribute_value_changed)
                #print 'group %s connected to attribute %s' % (self, member)
        self.on_device_set()
        self.parent.bind(ActivePalettes=self.on_parent_ActivePalettes_changed)
        
    @property
    def parent(self):
        if self.device is not None:
            return self.device
        return self._parent_group
    
        
    def on_device_set(self):
        pass
        
    def del_user(self, user):
        super(AttributeGroup, self).del_user(user)
        if user == self.captured_by:
            self.captured_by = None
        
    def _on_captured_by_set(self, **kwargs):
        value = kwargs.get('value')
        if value is not None:
            to_remove = set()
            for key, val in self.ActivePalettes.iteritems():
                if val is not None:
                    gr_id = '_'.join([self.id, key, str(val)])
                    group = self.parent.ActivePaletteData.get(key, {}).get('groups', {}).get(gr_id)
                    if group is not None and group != value:
                    #if value.ActivePalettes.get(key) != val:
                        to_remove.add(key)
            #print 'to_remove: ', to_remove
            for key in to_remove:
                p = self.find_ActivePalette_parent(category=key)
                if p:
                    p.del_active_palette(category=key)
#            if len(to_remove):
#                return
#        if self.device is None:
#            s = 'Group'
#        else:
#            s = 'Device'
#        print '%s AttrGrp %s captured_by=%s' % (s, self.name, kwargs.get('value'))
    
    def unlink(self):
        if self.valid:
            for member in self.members.values():
                member.disconnect(callback=self.on_attribute_value_changed)
        super(AttributeGroup, self).unlink()
    
    def search_for_members(self, **kwargs):
        return False
            
    def add_member(self, **kwargs):
        if 'update_values' in kwargs:
            update_values = kwargs.get('update_values')
            del kwargs['update_values']
        else:
            update_values = False
            if len(self.members) == 0:
                update_values = True
        super(AttributeGroup, self).add_member(**kwargs)
        if self.membership_lock:
            return
        for key, val in kwargs.iteritems():
            mvalue = val.value
            for realtype, emutype in EMULATED_TYPES.iteritems():
                if isinstance(mvalue, emutype):
                    mvalue = realtype(mvalue)
            self.member_values.update({key:val.value})
            for c in val.categories.itervalues():
                if c.id not in self.categories:
                    self.add_category(c)
        if not update_values:
            return update_values
        if self.device is not None:
            l = self.value
            if not isinstance(l, list) or not isinstance(l, dict):
                l = [self.value]
            if update_values and len(self.members) == len(l):
                self.value = self.get_value_from_attributes()
        else:
            if len(self.members):
                self.unbind(self._on_value_set)
                self.value = self.members.values()[0].value
                self.bind(value=self._on_value_set)
        return update_values
        
    def del_member(self, key):
        super(AttributeGroup, self).del_member(key)
        if key in self.member_values and not self.membership_lock:
            del self.member_values[key]
    
    def update_user_value(self, user=None, htp=False):
        if not len(self.users):
            return
        if user:
            value = user.member_values[self.id]
            captured_by = user
            #print 'group ltp=', value
        else:
            if isinstance(self.value, list):
                uvals = [[u.member_values[self.id][x] for x in range(len(self.value))] for u in self.users]
                value = [max(uval[x]) for x, uval in enumerate(uvals)]
            elif isinstance(self.value, dict):
                value = dict(zip(self.value.keys(), [max([u.member_values[self.id][key] for u in self.users]) for key in self.value.keys()]))
            else:
                value = max([u.member_values[self.id] for u in self.users])
            #if htp:
            #    if value <= self.value:
            #        return
            captured_by = None
            #print 'group htp=', value
        self.value_set_by_parent = True
        self.value = value
        self.captured_by = captured_by
            
    def clean_member_values(self):
        keys = set()
        for key in self.member_values.iterkeys():
            if key not in self.members:
                keys.add(key)
        for key in keys:
            del self.member_values[key]
        
    ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
#    def send_value_to_osc(self):
#        if self.osc_enabled:
#            self.osc_node.send_message(address='set-value', value=self.value)
            
    def on_value_change(self, **kwargs):
        ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        #if not self.value_set_by_osc and not self.value_set_by_parent:
        #    self.send_value_to_osc(**kwargs)
        #self.value_set_by_osc = False
        if not self.value_set_by_parent:
            self.captured_by = None
        self.value_set_by_parent = False
        
    def on_attribute_value_changed(self, **kwargs):
        for member in self.members.itervalues():
            if member.value_set_by_group:
                return
        self.value = self.get_value_from_attributes()
    
    ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
#    def on_osc_set_value(self, **kwargs):
#        value = kwargs.get('values')
#        self.value_set_by_osc = True
#        if len(value) == 1:
#            self.value = value[0]
#        else:
#            self.value = value
#        
#    def on_osc_request_value(self, **kwargs):
#        if self.osc_node.oscMaster:
#            self.send_value_to_osc()

    def on_parent_ActivePalettes_changed(self, **kwargs):
        value = kwargs.get('value')
        for category in self.categories.itervalues():
            i = value.get(category.name)
            self.ActivePalettes[category.name] = i
#            if i is not None:
#                gr_id = '_'.join([self.id, category.name, str(i)])
#                groups = self.parent.ActivePaletteData[category.name]['groups']
#                if groups is None:
#                    return
#                group = groups.get(gr_id)
#                self.palette_parents[category.name] = group
#            else:
#                self.palette_parents[category.name] = None
        
    @property
    def has_active_palettes(self):
        for key, val in self.ActivePalettes.iteritems():
            if val is not None:
                return True
                
    def find_ActivePalette_parent(self, **kwargs):
        category = kwargs.get('category')
        if self.ActivePalettes.get(category) is None:
            return False
        p = self.parent.find_ActivePalette_parent(**kwargs)
        if p:
            return p
        for user in self.users:
            p = user.find_ActivePalette_parent(**kwargs)
            if p:
                return p
        return False
        
    def get_values(self):
        d = {'group_value':self.value, 'member_values':{}}
        for key, val in self.members.iteritems():
            if val.captured_by != self:
                if getattr(val, 'base_class') == 'Group':
                    d.update({key:val.get_values()})
                else:
                    d.update({key:val.value})
        return d
        
    def get_objects(self, **kwargs):
        d = kwargs.get('obj_dict', {})
        captured_only = kwargs.get('captured_only', False)
        for key in ['DeviceGroup', 'AttributeGroup', 'ParameterGroup', 'Attribute']:
            if key not in d:
                d.update({key:{}})
        
        # TODO: this logic can be less dumb but i don't want to think
        key = 'AttributeGroup'
        if isinstance(self, ParameterGroup):
            key = 'ParameterGroup'
        if captured_only:
            if self.captured_by is None:
                d[key].update({self.id:self})
        else:
            d[key].update({self.id:self})
            
        if 'obj_dict' not in kwargs:
            kwargs['obj_dict'] = d
#        for key, val in self.members.iteritems():
#            if val.captured_by != self.id:
#                if isinstance(val, AttributeGroup):
#                    val.get_objects(**kwargs)
#                elif val.saved_class_name == 'PatchedAttribute':
#                    d['Attribute'].update({key:val})
        return d
        
    def request_values_from_osc(self, **kwargs):
        for h in self.osc_handlers.itervalues():
            nkwargs = kwargs.copy()
            if h.Property is not None:
                h.request_Property_value(**nkwargs)
            else:
                nkwargs['address'] = 'current-value'
                h.osc_node.send_message(**nkwargs)

class ProxyGroup(AttributeGroup):
    _saved_class_name = 'Proxy'
    def __init__(self, **kwargs):
        self._value = 0
        super(ProxyGroup, self).__init__(**kwargs)
        
    def search_for_members(self, **kwargs):
        groups = kwargs.get('groups')
        for group in groups:
            d = {group.id:group}
            self.add_member(**d)
        super(ProxyGroup, self).search_for_members(**kwargs)
        return True
    def add_member(self, **kwargs):
        super(ProxyGroup, self).add_member(**kwargs)
        if len(kwargs) and not self.membership_lock:
            uval = kwargs[kwargs.keys()[0]].value
            if type(uval) != type(self._value):
                self._value = type(uval)
                if type(uval) == list:
                    self._value = [0] * len(uval)
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        self._value = value
        for key in self.member_values.iterkeys():
            self.member_values[key] = value
        for member in self.members.itervalues():
            member.update_user_value(self)
        self.edited = True
        self.on_value_change()
        self.emit('value_changed', value=self.value, attribute=self)
        
class DimmerGroup(AttributeGroup):
    _saved_class_name = 'Dimmer'
    _Properties = {'value':dict(default=0., fget='_value_getter', fvalidate='_value_validate', 
                                min=0., max=100., quiet=True, threaded=True)}
    #_saved_attributes = ['scale']
    precedence_mode = 'htp'
    value_min = 0
    value_max = 100
    value_symbol = '%'
    def __init__(self, **kwargs):
        self._value = 0
        self._scale = 1.
        self.color_group = None
        self.submaster = kwargs.get('submaster', False)
        if self.submaster:
            self._value = self.value_max
        super(DimmerGroup, self).__init__(**kwargs)
        self.bind(value=self._on_value_set)
        
        ## ADDED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        self.add_osc_handler(Property='value')
        
    def search_for_members(self, **kwargs):
        valid = False
        if self.device is not None:
            found = None
            for attrib in self.device.Attributes.itervalues():
                if attrib.name == 'Dimmer':
                    found = attrib
            if found:
                self.add_member(dimmer=found)
            else:
                self.color_group = self.device.Groups.get('Color')
            valid = len(self.members) > 0 or self.color_group is not None
        return valid or super(DimmerGroup, self).search_for_members(**kwargs)
        
    def add_member(self, **kwargs):
        if self.submaster:
            kwargs['update_values'] = False
        super(DimmerGroup, self).add_member(**kwargs)
        
    def _value_getter(self):
        return self.Properties['value'].value * self.scale
    def _value_validate(self, value):
        return True
    def _on_value_set(self, **kwargs):
        value = kwargs.get('value')
        if self.scale == 0:
            self.Properties['value'].value = 0.
        else:
            self.Properties['value'].value = value / self.scale
        if self.color_group is not None:
            self.color_group.value_set_by_parent = True
            self.color_group.set_hsv(value=value)
            #self.color_group.color = [value] * 3
        else:
            if self.submaster:
                scale = self.value / self.value_max
                for m in self.members.itervalues():
                    m.scale = scale
            else:
                for key in self.member_values.iterkeys():
                    m = self.members.get(key)
                    if m:
                        if isinstance(m, DimmerGroup):
                            self.member_values[key] = value
                        else:
                            self.member_values[key] = value * 255. / self.value_max
                        m.update_user_value(self)
        self.edited = True
        self.on_value_change()
        
#    @property
#    def value(self):
#        return self._value * self.scale
#    @value.setter
#    def value(self, value):
#        if type(value) == list:
#            value = value[0]
#        if value < self.value_min or value > self.value_max:
#            return
#        if self.scale == 0:
#            self._value = 0
#        else:
#            self._value = value / self.scale
#        if self.color_group is not None:
#            self.color_group.value_set_by_parent = True
#            self.color_group.set_hsv(value=value)
#            #self.color_group.color = [value] * 3
#        else:
#            if self.submaster:
#                scale = self.value / self.value_max
#                for m in self.members.itervalues():
#                    m.scale = scale
#            else:
#                for key in self.member_values.iterkeys():
#                    m = self.members.get(key)
#                    if m:
#                        if isinstance(m, DimmerGroup):
#                            self.member_values[key] = value
#                        else:
#                            self.member_values[key] = value * 255. / self.value_max
#                        m.update_user_value(self)
#        self.edited = True
#        self.on_value_change()
#        self.emit('value_changed', value=self.value, attribute=self)
        
    @property
    def scale(self):
        return self._scale
    @scale.setter
    def scale(self, value):
        self._scale = value
        scaled = self.value * 255. / self.value_max
        for key in self.member_values.iterkeys():
            m = self.members.get(key)
            if isinstance(m, DimmerGroup):
                m.scale = value
            else:
                self.member_values[key] = scaled
                m.update_user_value()
        self.on_value_change()
        self.emit('value_changed', value=self.value, attribute=self)
        
    def get_value_from_attributes(self):
        if self.submaster:
            return self.value
        if self.color_group is not None:
            return self.color_group._hsv[2]
        member = self.members.values()[0]
        value = member.value
        if isinstance(member, DimmerGroup):
            value = value / member.scale
        return value

class ColorGroup(AttributeGroup):
    _saved_class_name = 'Color'
    color_keys = ['red', 'green', 'blue']
    hsv_keys = ['hue', 'sat', 'val']
    all_keys = color_keys + hsv_keys
    value_attribute = 'rgb_prop'
    value_scale = dict(zip(['hue', 'sat', 'val'], [1.0]*3))
    value_wrap_around = dict(hue=True, sat=False, val=False)
    precedence_mode = 'ltp'
    _Properties = {'value':dict(default=dict(zip(hsv_keys, [0.]*3)), 
                                min=dict(zip(hsv_keys, [0.]*3)), 
                                max=dict(zip(hsv_keys, [1.]*3)), 
                                quiet=True, 
                                threaded=True), 
                   'rgb_prop':dict(default=dict(zip(color_keys, [0.]*3)), 
                                   min=dict(zip(color_keys, [0.]*3)), 
                                   max=dict(zip(color_keys, [255.]*3)), 
                                   quiet=True, 
                                   threaded=True)}
    def __init__(self, **kwargs):
        self._rgb = [0, 0, 0]
        self._hsv = [0, 0, 0]
        super(ColorGroup, self).__init__(**kwargs)
        self.rgb_prop_set_by_program = False
        self.bind(value=self._on_value_set, 
                  rgb_prop=self._on_rgb_prop_set)
        
        ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        #d = dict(zip(self.all_keys, [self.on_osc_set_color] * len(self.all_keys)))
        #d.update(dict(zip(['rgb', 'hsv'], [self.on_osc_set_color]*2)))
        #self.add_osc_handler(callbacks=d)
        
        ## ADDED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        self.add_osc_handler(Property='value')
        
    def search_for_members(self, **kwargs):
        valid = False
        if self.device is not None:
            for attrib in self.device.Attributes.itervalues():
                if attrib.name.lower() in self.color_keys:
                    self.add_member(**{attrib.name.lower():attrib})
            valid = len(self.members) >= 3
        return valid or super(ColorGroup, self).search_for_members(**kwargs)
        
    def add_member(self, **kwargs):
        update_values = super(ColorGroup, self).add_member(**kwargs)
        if not update_values:
            return update_values
        self._on_value_set(value=self.value)
        
    def set_hsv(self, **kwargs):
        #hsv = [kwargs.get(key, self._hsv[x]) for x, key in enumerate(self.hsv_keys)]
        #self.color = colorsys.hsv_to_rgb(*hsv)
        self._set_color(**kwargs)
        
    @property
    def hsv(self):
        return dict(zip(self.hsv_keys, self._hsv))
    @hsv.setter
    def hsv(self, value):
        self._set_color(**value)
    
    @property
    def hue(self):
        return self.value['hue']
    @hue.setter
    def hue(self, value):
        self.value = {'hue':value}
    @property
    def sat(self):
        return self.value['sat']
    @sat.setter
    def sat(self, value):
        self.value = {'sat':value}
    @property
    def val(self):
        return self.value['val']
    @val.setter
    def val(self, value):
        self.value = {'val':value}
        
#    @property
#    def hue(self):
#        return self._hsv[0]
#    @hue.setter
#    def hue(self, value):
#        self._set_color(hue=value)
#    @property
#    def sat(self):
#        return self._hsv[1]
#    @sat.setter
#    def sat(self, value):
#        self._set_color(sat=value)
#    @property
#    def val(self):
#        return self._hsv[2]
#    @val.setter
#    def val(self, value):
#        self._set_color(val=value)
    @property
    def red(self):
        return self._rgb[0]
    @red.setter
    def red(self, value):
        self._set_color(red=value)
    @property
    def green(self):
        return self._rgb[1]
    @green.setter
    def green(self, value):
        self._set_color(green=value)
    @property
    def blue(self):
        return self._rgb[2]
    @blue.setter
    def blue(self, value):
        self._set_color(blue=value)
    @property
    def _value(self):
        return self._rgb
    @_value.setter
    def _value(self, value):
        self._rgb = value
#    @property
#    def value(self):
#        return self.rgb
#    @value.setter
#    def value(self, value):
#        self.rgb = value
    @property
    def rgb(self):
        return self._rgb
    @rgb.setter
    def rgb(self, value):
        self._set_color(rgb=value)
        
    def _on_value_set(self, **kwargs):
        value = kwargs.get('value')
        self._set_color(hsv=[value[key] for key in self.hsv_keys])
        
    def _on_rgb_prop_set(self, **kwargs):
        if self.rgb_prop_set_by_program:
            return
        value = kwargs.get('value')
        self._set_color(rgb=[value[key] for key in self.color_keys])
        
    def _set_color(self, **kwargs):
        def set_rgb(rgb):
            self._rgb = rgb
            newhsv = [self.value[key] for key in self.hsv_keys]
            if rgb == [0]*3:
                newhsv[2] = 0.
            else:
                rgb = [x / 255. for x in rgb]
                newhsv = list(colorsys.rgb_to_hsv(*rgb))
                self.value = dict(zip(self.hsv_keys, newhsv))
                #if hasrgb:
                #    print 'hsv:', self._hsv
            #self.Properties['value'].set_value(dict(zip(self.hsv_keys, newhsv)))
            do_updates()
        def set_hsv(hsv):
            #self._hsv = hsv
            rgbvals = [x * 255 for x in colorsys.hsv_to_rgb(*hsv)]
            self._rgb = rgbvals
            self.rgb_prop_set_by_program = True
            self.rgb_prop = dict(zip(self.color_keys, rgbvals))
            self.rgb_prop_set_by_program = False
            #print 'group hsv = %s, rgb = %s' % (hsv, self._rgb)
            do_updates()
        def do_updates():
            self.set_attributes()
            self.edited = True
            self.on_value_change(hsv=[self.value[key] for key in self.hsv_keys])
            #self.emit('value_changed', value=self.rgb, attribute=self)
            return
            
        rgb = kwargs.get('rgb')
        hsv = kwargs.get('hsv')
        #if hsv is None:
        #    hsv = [kwargs.get(key, self._hsv[x]) for x, key in enumerate(self.hsv_keys)]
        if hsv is not None:
            set_hsv(hsv)
            
        if rgb is None:
            hasrgb = False
            for key in self.color_keys:
                if key in kwargs:
                    hasrgb = True
            if hasrgb:
                rgb = [kwargs.get(key, self._rgb[x]) for x, key in enumerate(self.color_keys)]
        if rgb is not None:
            set_rgb(rgb)
        
    def get_value_from_attributes(self):
        return [self.members[key].value for key in self.color_keys if key in self.members]
        
    def set_attributes(self):
#        for x, val in enumerate(self._rgb):
#            attrib = self.members.get(self.color_keys[x])
#            if attrib:
#                self.member_values.update({attrib.id:val})
#                attrib.update_user_value(self)
        for key in self.members.keys()[:]:
            val = self.members.get(key)
            if val is None:
                continue
            if isinstance(val, GroupBase):
                self.member_values[key] = self.value
            elif key in self.color_keys:
                self.member_values[key] = self.rgb_prop[key]
                #self.member_values.update({key:self._rgb[self.color_keys.index(key)]})
            val.update_user_value(self)
    
    ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
#    def send_value_to_osc(self, **kwargs):
#        if self.osc_enabled:
#            if len(kwargs):
#                for key, val in kwargs.iteritems():
#                    self.osc_node.send_message(address=key, value=val)
#                return
#        super(ColorGroup, self).send_value_to_osc(**kwargs)
#    
#    def on_osc_set_color(self, **kwargs):
#        key = kwargs.get('method')
#        value = kwargs.get('values')
#        if key not in ['hsv', 'rgb']:
#            value = value[0]
#        self.value_set_by_osc = True
#        self._set_color(**{key:value})
    
    def on_member_value_changed(self, **kwargs):
        attrib = kwargs.get('attribute')
        value = kwargs.get('value')
        self._rgb[self.color_keys.index(attrib.name.lower())] = value
        #print 'group member_value_changed:', self.name, self._value

class PositionGroup(AttributeGroup):
    _saved_class_name = 'Position'
    pos_keys = ['pan', 'tilt']
    precedence_mode = 'ltp'
    #value_attribute = 'value_pos'
    value_keys = ['pan', 'tilt']
    #value_symbol = unichr(176).encode('latin-1')
    value_symbol = ' deg'
    _Properties = {'pan_range':dict(default=20), 
                   'tilt_range':dict(default=20), 
                   'pan_invert':dict(default=False), 
                   'tilt_invert':dict(default=False), 
                   'pan':dict(default=0., min=-10., max=10., quiet=True, threaded=True), 
                   'tilt':dict(default=0., min=-10, max=10., quiet=True, threaded=True), 
                   'value':dict(default={'pan':0., 'tilt':0.}, 
                                min=dict(zip(pos_keys, [-10.]*2)), 
                                max=dict(zip(pos_keys, [10.]*2)), quiet=True, threaded=True)}
    _SettingsProperties = ['pan_range', 'tilt_range', 'pan_invert', 'tilt_invert']
    def __init__(self, **kwargs):
        self._pos = [0, 0]
        self._pos_invert = [False, False]
        prebind = kwargs.get('prebind', {})
        prebind.update(dict(pan_invert=self.on_pan_invert_set, 
                            tilt_invert=self.on_tilt_invert_set, 
                            pan=self._on_pan_set, 
                            tilt=self._on_tilt_set, 
                            pan_range=self._on_pan_range_set, 
                            tilt_range=self._on_tilt_range_set, 
                            value=self._on_value_set))
        kwargs['prebind'] = prebind
        super(PositionGroup, self).__init__(**kwargs)
        
        ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        #self.add_osc_handler(callbacks=dict(zip(self.pos_keys, [self.on_osc_set_pos]*2)))
        
        ## ADDED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
        self.add_osc_handler(Property='value')
        
    def search_for_members(self, **kwargs):
        valid = False
        if self.device is not None:
            attribs = {}
            for attrib in self.device.Attributes.itervalues():
                if attrib.name.lower() in self.pos_keys:
                    self.add_member(**{attrib.name.lower():attrib})
            valid = len(self.members) >= 2
        return valid or super(PositionGroup, self).search_for_members(**kwargs)
        
    def add_member(self, **kwargs):
        super(PositionGroup, self).add_member(**kwargs)
        for key, val in kwargs.iteritems():
            if isinstance(val, PositionGroup):
                for attr in ['pan_range', 'tilt_range']:
                    if getattr(self, attr) < getattr(val, attr):
                        setattr(self, attr, getattr(val, attr))
    
    def on_device_set(self):
        for key in ['pan_range', 'tilt_range']:
            value = getattr(self.device.definition, key)
            if value is not None:
                setattr(self, key, value)
        
    @property
    def _value(self):
        return self._pos
    @_value.setter
    def _value(self, value):
        self._pos = value
#    @property
#    def value(self):
#        return self._pos
#    @value.setter
#    def value(self, value):
#        self._set_pos(**dict(zip(self.pos_keys, value)))
    @property
    def value_pos(self):
        return dict(zip(self.pos_keys, self._pos))
    @value_pos.setter
    def value_pos(self, value):
        self._set_pos(**value)
    @property
    def value_seq(self):
        return self._pos
    @value_seq.setter
    def value_seq(self, value):
        self._set_pos(pan=value[0], tilt=value[1])
#    @property
#    def pan(self):
#        if self._pos_invert[0]:
#            return self._pos[0] * -1
#        return self._pos[0]
#    @pan.setter
#    def pan(self, value):
#        self._set_pos(pan=value)
#    @property
#    def tilt(self):
#        if self._pos_invert[1]:
#            return self._pos[1] * -1
#        return self._pos[1]
#    @tilt.setter
#    def tilt(self, value):
#        self._set_pos(tilt=value)
    def _pan_getter(self):
        #if self.pan_invert:
        #    return self._pos[0] * -1
        return self._pos[0]
    def _tilt_getter(self):
        #if self.tilt_invert:
        #    return self._pos[1] * -1
        return self._pos[1]
    def _on_pan_set(self, **kwargs):
        value = kwargs.get('value')
        if value is None:
            return
        #self._set_pos(pan=value)
        self.value['pan'] = value
    def _on_tilt_set(self, **kwargs):
        value = kwargs.get('value')
        if value is None:
            return
        #self._set_pos(tilt=value)
        self.value['tilt'] = value
        
#    def _value_getter(self):
#        return dict(zip(self.pos_keys, self._pos))
#    def _value_setter(self, value):
#        self._set_pos(**value)
    def _on_value_set(self, **kwargs):
        for key, val in self.value.iteritems():
            setattr(self, key, val)
        self._set_pos(**self.value)
            
    @property
    def x(self):
        return self.pan
    @x.setter
    def x(self, value):
        self._set_pos(x=value)
    @property
    def y(self):
        return self.tilt
    @y.setter
    def y(self, value):
        self._set_pos(y=value)
        
    @property
    def pos(self):
        keys = self.pos_keys + self.value_keys
        return dict(zip(keys, self._pos * 2))
    @pos.setter
    def pos(self, value):
        self._set_pos(**value)
        
#    @property
#    def pan_invert(self):
#        return self._pos_invert[0]
#    @pan_invert.setter
#    def pan_invert(self, value):
#        if value != self.pan_invert:
#            x = self.x
#            self._pos_invert[0] = value
#            self.x = x
#    @property
#    def tilt_invert(self):
#        return self._pos_invert[1]
#    @tilt_invert.setter
#    def tilt_invert(self, value):
#        if value != self.tilt_invert:
#            y = self.y
#            self._pos_invert[1] = value
#            self.y = y

    def on_pan_invert_set(self, **kwargs):
        value = kwargs.get('value')
        x = self.x
        self._pos_invert[0] = value
        self.x = x
    def on_tilt_invert_set(self, **kwargs):
        value = kwargs.get('value')
        y = self.y
        self._pos_invert[1] = value
        self.y = y
    def _on_pan_range_set(self, **kwargs):
        value = kwargs.get('value') / 2.
        self.Properties['pan'].range = [value * -1., value]
        self.Properties['value'].min['pan'] = value * -1.
        self.Properties['value'].max['pan'] = value
    def _on_tilt_range_set(self, **kwargs):
        value = kwargs.get('value') / 2.
        self.Properties['tilt'].range = [value * -1., value]
        self.Properties['value'].min['tilt'] = value * -1.
        self.Properties['value'].max['tilt'] = value
        
    @property
    def value_min(self):
        return dict(zip(['x', 'y'], [range / 2. * -1. for range in [self.pan_range, self.tilt_range]]))
    @property
    def value_max(self):
        return dict(zip(['x', 'y'], [range / 2. for range in [self.pan_range, self.tilt_range]]))
    @property
    def pos_range(self):
        return [self.pan_range, self.tilt_range]
    @property
    def value_scale(self):
        d = dict(zip(self.pos_keys, self.pos_range))
        for key in d:
            if d[key] is None:
                d[key] = 1
        return d
        
    def _set_pos(self, **kwargs):
        self.set_attributes()
        self.on_value_change()
        self.emit('value_changed', value=self.value, attribute=self)
        
    def old_set_pos(self, **kwargs):
        pos_change = [False, False]
        for key, val in kwargs.iteritems():
            if val is None:
                break
            if key in self.pos_keys:
                keys = self.pos_keys
            elif key in self.value_keys:
                keys = self.value_keys
            i = keys.index(key)
            if self._pos_invert[i]:
                val = val * -1
            if self._pos[i] != val:
                pos_change[i] = True
                self._pos[i] = val
        if pos_change[0] or pos_change[1]:
            self.set_attributes()
            keys = [key for i, key in enumerate(self.pos_keys) if pos_change[i]]
            self.on_value_change(**dict(zip(keys, self._pos)))
            #self.Properties['value'].emit(None)
            self.emit('value_changed', value=self.value, attribute=self)
        
    def get_value_from_attributes(self):
        if self.device is not None:
            l = [(self.members[key].value * self.pos_range[i] / 255.) - (self.pos_range[i] / 2.) for i, key in enumerate(self.pos_keys) if key in self.members]
            return dict(zip(self.pos_keys, l))
        if not self.members:
            return dict(zip(self.pos_keys, [0.]*2))
        return self.members.values()[0].value
        #return [self.members[key].value for key in self.pos_keys]
        
    def set_attributes(self):
#        for x, val in enumerate(self._pos):
#            attrib = self.members.get(self.pos_keys[x])
#            if attrib:
#                self.member_values.update({attrib.id:val})
#                attrib.update_user_value(self)
        for key in self.members.keys()[:]:
            val = self.members.get(key)
            if val is None:
                continue
            if isinstance(val, GroupBase):
                self.member_values.update({key:self.value})
            elif key in self.pos_keys:
                i = self.pos_keys.index(key)
                #value = (self._pos[i] + (self.pos_range[i] / 2.)) * 255. / self.pos_range[i]
                prop = self.Properties[key]
                value = prop.value
                if self._pos_invert[i]:
                    value = value * -1
                value = (value + prop.max) * 255. / (prop.max * 2)
                
                if value > 255:
                    bob
                self.member_values.update({key:value})
            val.update_user_value(self)
            
#    def on_member_value_changed(self, **kwargs):
#        attrib = kwargs.get('attribute')
#        value = kwargs.get('value')
#        self._pos[self.pos_keys.index(attrib.name.lower())] = value
    
    ## REMOVED TO TEST NEW OSCHandler PROPERTY FUNCTIONS
#    def send_value_to_osc(self, **kwargs):
#        if self.osc_enabled:
#            if len(kwargs):
#                for key, val in kwargs.iteritems():
#                    self.osc_node.send_message(address=key, value=val)
#                return
#        super(PositionGroup, self).send_value_to_osc(**kwargs)
#        
#    def on_osc_set_pos(self, **kwargs):
#        key = kwargs.get('method')
#        value = kwargs.get('values')
#        self.value_set_by_osc = True
#        self._set_pos(**{key:value})
        
class ParameterGroup(AttributeGroup):
    _saved_class_name = 'ParameterGroup'
    _Properties = {'current_parameter':dict(default={'name':None, 'value':0.}, quiet=True), 
                   'value':dict(type=str, fget='_value_getter', quiet=True)}
    value_attribute = 'current_parameter'
    signals_to_register = ['new_parameter', 'parameter_changed']
    def __init__(self, **kwargs):
        self.GlobalAttribute  = None
        self._active_parameter = None
        self.indexed_parameters = {}
        self.parameter_obj = {}
        self.parameters_by_name = {}
        self.attribute_name = kwargs.get('attribute_name')
        if self.attribute_name:
            kwargs.setdefault('name', self.attribute_name)
            kwargs.setdefault('osc_address', self.attribute_name)
        super(ParameterGroup, self).__init__(**kwargs)
        self.bind(current_parameter=self._on_current_parameter_set, 
                  value=self._on_value_set)
        
    def _on_current_parameter_set(self, **kwargs):
        old = kwargs.get('old')
        value = kwargs.get('value')
        param = self.parameter_obj.get(self.active_parameter)
        if value.get('name') is None:
            value['name'] = self.active_parameter
        if value['name'] != old['name']:
            self.active_parameter = value['name']
        if param is not None and value['value'] != old['value']:
            param.value = value['value']
       
        #print self.attribute_name, ' parameter set: ', self.current_parameter
            
#    @property
#    def value(self):
#        return self.active_parameter
#    @value.setter
#    def value(self, value):
#        self.active_parameter = value
    def _value_getter(self):
        return self.active_parameter
    def _on_value_set(self, **kwargs):
        value = kwargs.get('value')
        self.active_parameter = value
    @property
    def _value(self):
        return self._active_parameter
    @_value.setter
    def _value(self, value):
        self._active_parameter = value
    @property
    def active_parameter(self):
        return self._active_parameter
    @active_parameter.setter
    def active_parameter(self, value):
        if type(value) == list:
            if not len(value):
                return
            value = value[0]
        if not value:
            return
        if value == self.active_parameter:
            return
        param = self.parameter_obj[value]
        if isinstance(param, GroupParam):
            param.active = True
        #self.current_parameter['name'] = value
        #self.current_parameter['value'] = param.value
        self.set_attributes()
        self.on_value_change()
                
    def get_value_from_attributes(self):
        return self.active_parameter
        
    def set_attributes(self):
        for key in self.members.keys()[:]:
            val = self.members.get(key)
            if val is None:
                continue
            paramobj = self.parameter_obj.get(self.active_parameter)
            if isinstance(val, ParameterGroup):
                self.member_values.update({key:paramobj})
                val.update_user_value(self)
            else:
                if paramobj is not None:
                    param = val.indexed_parameters.get(paramobj.name)
                    if param:
                        value = param.calculate_value(paramobj.value)
                        self.member_values.update({key:value})
                        val.update_user_value(self, key)
                else:
                    self.member_values.update({key:0})
                    val.update_user_value(self, key)
                    
    def update_user_value(self, user, htp=False):
        if len(self.users):
            param = user.member_values[self.id]
            paramobj = self.parameter_obj.get(param.name)
            if not paramobj:
                return
            self.captured_by = user
            paramobj.value = param.value
            paramobj.active = True
            self.value_set_by_parent = True
                
    def search_for_members(self, **kwargs):
        valid = False
        if self.device is not None:
            for key, val in self.device.Attributes.iteritems():
                if val.name.lower() == self.attribute_name.lower():
                    self.add_member(**{key:val})
            valid = len(self.indexed_parameters) > 0

        return valid or super(ParameterGroup, self).search_for_members(**kwargs)
        
    def add_member(self, **kwargs):
        super(ParameterGroup, self).add_member(**kwargs)
        if not self.membership_lock:
            for member in kwargs.itervalues():
                if self.GlobalAttribute is None:
                    if isinstance(member, ParameterGroup):
                        self.GlobalAttribute = member.GlobalAttribute
                    else:
                        self.GlobalAttribute = member.DefinitionAttribute.GlobalAttribute
                    self.DefinitionAttribute = member.DefinitionAttribute
                self.update_parameters(new_member=member)
                #if self.device is not None:
                #    member.connect('parameter_changed', self.on_member_parameter_changed)
                
    def del_member(self, key):
        if not self.membership_lock:
            if self.device is not None:
                self.members[key].disconnect(callback=self.on_member_parameter_changed)
            super(ParameterGroup, self).del_member(key)
            self.update_parameters(cleanup=True)
        
    def update_parameters(self, **kwargs):
        new_member = kwargs.get('new_member')
        cleanup = kwargs.get('cleanup', False)
        if new_member is not None:
            for pkey, pval in new_member.indexed_parameters.iteritems():
                    flag = True
                    if flag:
                        if pkey not in self.indexed_parameters:
                            self.indexed_parameters.update({pkey:{}})
                        if isinstance(new_member, ParameterGroup):
                            obj = new_member.parameter_obj.get(pkey)
                            if obj:
                                if pkey not in self.parameter_obj:
                                    paramobj = GroupParam(name=pkey, parent_attribute=self)
                                    self.parameter_obj.update({pkey:paramobj})
                                    paramobj.bind(value=self.on_parameter_obj_value_set, 
                                                  active=self.on_parameter_obj_active_set)
                                range = obj.value_max - obj.value_min
                                paramobj = self.parameter_obj[pkey]
                                if range > paramobj.value_max:
                                    paramobj.value_max = range
                            self.indexed_parameters[pkey].update({new_member.id:obj})
                        else:
                            paramobj = GroupParam(name=pval.name, ignore_Property_range=True, value_min=pval.value_min, 
                                                  value_max=pval.value_max, parent_attribute=self)
                            if pval.value is not None:
                                paramobj.value = pval.value
                            self.parameter_obj.update({pkey:paramobj})
                            paramobj.bind(value=self.on_parameter_obj_value_set, 
                                          active=self.on_parameter_obj_active_set)
                            self.indexed_parameters[pkey].update({new_member.id:pval})
                        self.emit('new_parameter', group=self, name=pkey, id=pkey)
        if cleanup:
            dead_members = set()
            dead_params = set()
            for pkey, pval in self.indexed_parameters.iteritems():
                for mkey, mval in pval.iteritems():
                    if mkey not in self.members:
                        dead_members.add(mkey)
            for key in dead_members:
                for pkey, pval in self.indexed_parameters.iteritems():
                    if key in pval:
                        del pval[key]
                    if not pval:
                        dead_params.add(pkey)
            for key in dead_params:
                param = self.parameter_obj[key]
                param.unbind(self)
                del self.parameter_obj[key]
                del self.indexed_parameters[key]
                
    def on_attribute_value_changed(self, **kwargs):
        pass
        
    def on_member_parameter_changed(self, **kwargs):
        param = kwargs.get('parameter')
        if param.name in self.indexed_parameters and self._active_parameter != param.name:
            flag = True
            for key, val in self.indexed_parameters[param.name].iteritems():
                if not val.active:
                    flag = False
            if flag:
                self._active_parameter = param.name
                self.emit('parameter_changed', group=self, name=param.name, id=param.id)
                
    def on_parameter_obj_value_set(self, **kwargs):
        param = kwargs.get('obj')
        value = kwargs.get('value')
        self.active_parameter = param.name
        #if self.current_parameter['name'] != param.name:
        #    self.current_parameter['name'] = param.name
        self.current_parameter['value'] = value
        self.set_attributes()
        
    def on_parameter_obj_active_set(self, **kwargs):
        param = kwargs.get('obj')
        state = kwargs.get('value')
        if state:
            if self.active_parameter != param.name:
                self._active_parameter = param.name
                self.current_parameter = {'name':param.name, 'value':param.value}
                if param.value is None:
                    bob
                #self.current_parameter['name'] = param.name
                #self.current_parameter['value'] = param.value
            for p in self.parameter_obj.itervalues():
                if p != param and isinstance(p, GroupParam):
                    p.active = False
            self.set_attributes()
            self.emit('parameter_changed', group=self, name=param.name, id=param.id)

class GroupParam(OSCBaseObject):
    _Properties = {'value':dict(default=0, min=0, max=1, quiet=True, ignore_type=True), 
                   'active':dict(default=False, quiet=True)}
    def __init__(self, **kwargs):
        self.value_set_by_osc = False
        self.parent_attribute = kwargs.get('parent_attribute')
        if self.parent_attribute.osc_enabled:
            kwargs.setdefault('osc_address', kwargs.get('name'))
            kwargs.setdefault('osc_parent_node', self.parent_attribute.osc_node)
        super(GroupParam, self).__init__(**kwargs)
        self.Properties['value'].ignore_range = kwargs.get('ignore_Property_range', False)
        self.value_min = kwargs.get('value_min', 0)
        self.value_max = kwargs.get('value_max', 1)
        self.bind(value=self._on_value_set)
        #self.register_signal('active_changed', 'value_changed')
        self.id = setID(kwargs.get('id'))
        self.name = kwargs.get('name')
        self.add_osc_handler(callbacks={'set-value':self.on_osc_set_value, 
                                        'current-value':self.on_osc_request_value})
                                        
    
    def _on_value_set(self, **kwargs):
        value = kwargs.get('value')
        if not self.value_set_by_osc and self.osc_enabled:
            self.osc_node.send_message(address='set-value', value=value)
        self.value_set_by_osc = False
    
    @property
    def value_min(self):
        return self.Properties['value'].min
    @value_min.setter
    def value_min(self, value):
        self.Properties['value'].min = value
    @property
    def value_max(self):
        return self.Properties['value'].max
    @value_max.setter
    def value_max(self, value):
        self.Properties['value'].max = value    
        
    def on_osc_set_value(self, **kwargs):
        value = kwargs.get('values')[0]
        if value != self.value:
            self.value_set_by_osc = True
            self.value = value
        
    def on_osc_request_value(self, **kwargs):
        if self.osc_enabled:
            self.osc_node.send_message(address='set-value', value=self.value)
            
class PowerControlGroup(AttributeGroup):
    _saved_class_name = 'PowerControl'
    def __init__(self, **kwargs):
        self.attribute_name = 'Power'
        super(PowerControlGroup, self).__init__(**kwargs)
        
    @property
    def value(self):
        return self._value
    @value.setter
    def value(self, value):
        self._value = value
        if value:
            i = 0
        else:
            i = 255
        for key in self.member_values.iterkeys():
            self.member_values[key] = i
            m = self.members.get(key)
            if m:
                m.update_user_value(self)
        self.edited = True
        self.on_value_change()
        self.emit('value_changed', value=self.value, attribute=self)
    
    

groupClasses = {}
for cls in [DimmerGroup, ColorGroup, PositionGroup]:
    groupClasses.update({cls._saved_class_name:cls})
